#include <dspSimulator/instruction/cmpgtu.h>

bool CMPGTU::checkCondition(std::shared_ptr<DspMachine> dspMachine) {
    if (isCompactInstruction)
        return true;
    else
        return checkStandardCondition(dspMachine);
}

void CMPGTU::loadInstIRIntoJit(llvm::LLVMContext *llvmContext, llvm::Module *module,
                               std::shared_ptr<llvm::IRBuilder<>> irBuilder) {
    llvm::FunctionType *funcType = llvm::FunctionType::get(irBuilder->getVoidTy(), false);
    // --------------------------------- cmpgtu (.unit) uint,xuint, uint---------------------------------------------
    {
        llvm::Function *cmpltuL = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "CMPGTUKind1",
                                                         module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", cmpltuL);
        llvm::BasicBlock *thenBB = llvm::BasicBlock::Create(*llvmContext, "then", cmpltuL);
        llvm::BasicBlock *elseBB = llvm::BasicBlock::Create(*llvmContext, "else", cmpltuL);
        llvm::BasicBlock *endBB = llvm::BasicBlock::Create(*llvmContext, "end", cmpltuL);
        irBuilder->SetInsertPoint(entryBB);

        auto dstRegPtr = getDstLoRegPtr(module, irBuilder);

        auto src2RegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2 = irBuilder->CreateLoad(src2RegPtr);

        auto src1RegPtr = getSrc1LoRegPtr(module, irBuilder);
        auto src1 = irBuilder->CreateLoad(src1RegPtr);

        auto resPtr = irBuilder->CreateAlloca(irBuilder->getInt32Ty());

        auto cond = irBuilder->CreateICmpUGT(src1, src2);
        irBuilder->CreateCondBr(cond, thenBB, elseBB);

        irBuilder->SetInsertPoint(thenBB);
        irBuilder->CreateStore(irBuilder->getInt32(1), resPtr);
        irBuilder->CreateBr(endBB);

        irBuilder->SetInsertPoint(elseBB);
        irBuilder->CreateStore(irBuilder->getInt32(0), resPtr);
        irBuilder->CreateBr(endBB);

        irBuilder->SetInsertPoint(endBB);
        auto res = irBuilder->CreateLoad(resPtr);
        irBuilder->CreateStore(res, dstRegPtr);
        irBuilder->CreateRetVoid();
    }

    // --------------------------------- cmpgtu (.unit) ucst4,xuint, uint---------------------------------------------
    {
        llvm::Function *cmpltuL = llvm::Function::Create(funcType, llvm::GlobalValue::ExternalLinkage, "CMPGTUKind2",
                                                         module);
        llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(*llvmContext, "entry", cmpltuL);
        llvm::BasicBlock *thenBB = llvm::BasicBlock::Create(*llvmContext, "then", cmpltuL);
        llvm::BasicBlock *elseBB = llvm::BasicBlock::Create(*llvmContext, "else", cmpltuL);
        llvm::BasicBlock *endBB = llvm::BasicBlock::Create(*llvmContext, "end", cmpltuL);
        irBuilder->SetInsertPoint(entryBB);

        auto dstRegPtr = getDstLoRegPtr(module, irBuilder);

        auto src2RegPtr = getSrc2LoRegPtr(module, irBuilder);
        auto src2 = irBuilder->CreateLoad(src2RegPtr, "src2Reg");

        auto cst4 = getCstN(module, irBuilder, 4);
        auto ucst4 = irBuilder->CreateZExt(cst4, irBuilder->getInt32Ty());

        auto resPtr = irBuilder->CreateAlloca(irBuilder->getInt32Ty());

        auto cond = irBuilder->CreateICmpUGT(ucst4, src2);
        irBuilder->CreateCondBr(cond, thenBB, elseBB);

        irBuilder->SetInsertPoint(thenBB);
        irBuilder->CreateStore(irBuilder->getInt32(1), resPtr);
        irBuilder->CreateBr(endBB);

        irBuilder->SetInsertPoint(elseBB);
        irBuilder->CreateStore(irBuilder->getInt32(0), resPtr);
        irBuilder->CreateBr(endBB);

        irBuilder->SetInsertPoint(endBB);
        auto res = irBuilder->CreateLoad(resPtr);
        irBuilder->CreateStore(res, dstRegPtr);

        irBuilder->CreateRetVoid();
    }
}

std::shared_ptr<Instruction> CMPGTU::decode(std::shared_ptr<InstInfo> instInfo, u64 baseCycle) {
    std::shared_ptr<CMPGTU> cmpgtu;
    auto dspInstInfo = std::dynamic_pointer_cast<DspInstInfo>(instInfo);
    bool isStandardInst = dspInstInfo->isStandardInstruction();
    if (isStandardInst) {
        auto data = vectorToBitSet<32>(dspInstInfo->getBits());
        if (checkBits<10>(data, 2, 0b1001110110) && existStandardCondition(data)) {
            cmpgtu = std::make_shared<CMPGTU>(dspInstInfo->getAddress(), baseCycle);
            cmpgtu->creg_z = extractBits<4>(data, 28);
            cmpgtu->dst = extractBits<5>(data, 23).to_ulong();
            cmpgtu->src2 = extractBits<5>(data, 18).to_ulong();
            cmpgtu->cst = extractBits<5>(data, 13).to_ulong();
            cmpgtu->x = data[12];
            cmpgtu->s = data[1];
            cmpgtu->p = data[0];
            if (cmpgtu->s == 0) {
                cmpgtu->funcUnit = FuncUnit::L1;
            } else {
                cmpgtu->funcUnit = FuncUnit::L2;
            }
            cmpgtu->instFormat = InstFormat::Kind2;
        } else if (checkBits<10>(data, 2, 0b1001111110) && existStandardCondition(data)) {
            cmpgtu = std::make_shared<CMPGTU>(dspInstInfo->getAddress(), baseCycle);
            cmpgtu->creg_z = extractBits<4>(data, 28);
            cmpgtu->dst = extractBits<5>(data, 23).to_ulong();
            cmpgtu->src2 = extractBits<5>(data, 18).to_ulong();
            cmpgtu->src1 = extractBits<5>(data, 13).to_ulong();
            cmpgtu->x = data[12];
            cmpgtu->s = data[1];
            cmpgtu->p = data[0];
            if (cmpgtu->s == 0) {
                cmpgtu->funcUnit = FuncUnit::L1;
            } else {
                cmpgtu->funcUnit = FuncUnit::L2;
            }
            cmpgtu->instFormat = InstFormat::Kind1;
        }
    } else {
        auto data = vectorToBitSet<16>(dspInstInfo->getBits());
        if (checkBits<3>(data, 1, 0b100) && data[10] == 1 && data[11] == 1 && checkBits<2>(data, 5, 0b11)) {
            cmpgtu = std::make_shared<CMPGTU>(dspInstInfo->getAddress(), baseCycle);
            cmpgtu->isCompactInstruction = true;
            cmpgtu->compactInstKind = "L2c";
            cmpgtu->src1 = extractBits<3>(data, 13).to_ulong();
            cmpgtu->x = data[12];
            cmpgtu->src2 = extractBits<3>(data, 7).to_ulong();
            cmpgtu->dst = data[4];
            cmpgtu->s = data[0];
            if (cmpgtu->s == 0) {
                cmpgtu->funcUnit = FuncUnit::L1;
            } else {
                cmpgtu->funcUnit = FuncUnit::L2;
            }
            cmpgtu->PROT = dspInstInfo->getPROT();
            cmpgtu->SAT = dspInstInfo->getSAT();
            cmpgtu->BR = dspInstInfo->getBR();
            cmpgtu->RS = dspInstInfo->getRS();
            cmpgtu->DSZ = dspInstInfo->getDSZ();
            cmpgtu->p = dspInstInfo->getP();
            cmpgtu->fixUpRegOnCompactInstruction();
            cmpgtu->instFormat = InstFormat::Kind1;
        } else if (checkBits<6>(data, 1, 0b010011) && data[10] == 0 && data[12] == 1 && checkBits<2>(data, 14, 0b11)) {
            cmpgtu = std::make_shared<CMPGTU>(dspInstInfo->getAddress(), baseCycle);
            cmpgtu->isCompactInstruction = true;
            cmpgtu->compactInstKind = "Lx1c";
            cmpgtu->cst = data[13];
            cmpgtu->dst = data[11];
            cmpgtu->src2 = extractBits<3>(data, 7).to_ulong();
            cmpgtu->s = data[0];
            if (cmpgtu->s == 0) {
                cmpgtu->funcUnit = FuncUnit::L1;
            } else {
                cmpgtu->funcUnit = FuncUnit::L2;
            }
            cmpgtu->PROT = dspInstInfo->getPROT();
            cmpgtu->SAT = dspInstInfo->getSAT();
            cmpgtu->BR = dspInstInfo->getBR();
            cmpgtu->RS = dspInstInfo->getRS();
            cmpgtu->DSZ = dspInstInfo->getDSZ();
            cmpgtu->p = dspInstInfo->getP();
            cmpgtu->fixUpRegOnCompactInstruction();
            cmpgtu->instFormat = InstFormat::Kind2;
        }
    }
    return cmpgtu;
}

void CMPGTU::executeCustom(std::shared_ptr<TargetMachine> targetMachine, ExecutePhase executePhase) {

}

std::string CMPGTU::toString() const {
    std::string res;
    if (!isCompactInstruction) {
        res += getStandardConditionString() + " ";
    }
    switch (instFormat) {
        case InstFormat::Kind1:
            res += m_name + " " + getSrc1Reg32Name() + "," + getSrc2Reg32Name() + "," +
                   getDstReg32Name();
            break;
        case InstFormat::Kind2:
            res += m_name + " " + dec2hex(cst) + "," + getSrc2Reg32Name() + "," +
                   getDstReg32Name();
            break;
        default:
            break;
    }
    return res;
}

std::shared_ptr<DspInstruction> CMPGTU::clone() {
    return std::make_shared<CMPGTU>(*this);
}
